#include "ReadsMapping.h"
#include "MappingResult.h"
#ifdef _OPENMP
#include <omp.h>
#endif

const int MISQUERYCHECKPOINT = 100000;

int parallelMappingLongReads(vector<string>& readSetsList,\
                             GIndexTQ& indexTable, MappingOpts P)
{
    P.clearOutputFileName(readSetsList.size() > 1);
    indexTable.bExcludeAmbiguous = P.bExcludeAmbiguousReads;
    //__OPENMP_FOR_PARALLEL__(#pragma)
    int i;
#ifdef _OPENMP
    int numberOfCPUs = omp_get_num_procs();
    LOG_INFO("\nInfo %d: %d CPUs detected. %s.\n",\
             INFO_LOG, numberOfCPUs, BLANK_LINE);
    #pragma omp parallel for
#endif
    for (i = 0; i < (int)readSetsList.size(); i++) {
        CReadsMapping mapping(P);
        const char* readSetName = (readSetsList.at(i)).c_str();
        if (checkFileExist(readSetName)) {
            CLongReadsSet longReadSet(readSetName, P.readsFileFormat, P.readLength,\
                                      P.allowedNumOfNinRead, P.truncatedReadPrefix);
            if (P.bIgnoreQS) {
                longReadSet.ignoreQScores();
            }
            TIME_INFO(mapping.mapReadsInAFile(longReadSet, indexTable), "Mapping takes");
        }
    }
    return(0);
}

// Given a read set list and the index table, this function maps reads in parallel
int parallelMapping(vector<string>& readSetsList, \
                    GIndexTQ& indexTable, MappingOpts P)
{
    P.clearOutputFileName(readSetsList.size() > 1);
    //__OPENMP_FOR_PARALLEL__(#pragma)
    int i;
#ifdef _OPENMP
    int numberOfCPUs = omp_get_num_procs();
    LOG_INFO("\nInfo %d: %d CPUs detected. %s.\n",\
             INFO_LOG, numberOfCPUs, BLANK_LINE);
    #pragma omp parallel for
#endif
    for (i = 0; i < (int)readSetsList.size(); i++) {
        const char* inputFileN = readSetsList.at(i).c_str();
        CReadInBitsSet readSet(inputFileN, P.readsFileFormat, indexTable.uiRead_Length, P.allowedNumOfNinRead, P.truncatedReadPrefix);
        if (P.bIgnoreQS) {
            readSet.ignoreQScores();
        }
        CReadsMapping mapping(P);
        TIME_INFO(mapping.mapReadsInAFile(readSet, indexTable), "Mapping completed");
    }
    return(0);
}

CReadsMapping::CReadsMapping(void)
{
    this->initialization();
}

CReadsMapping::CReadsMapping(MappingOpts P)
{
    this->initialization();
    this->opt= P;

    this->iMultiMappedLocationThreshold = P.maxAlignPerRead;
    alignmentsQ[0].iMaxCapacity = P.maxAlignPerRead + 1; // store one more record
    alignmentsQ[1].iMaxCapacity = P.maxAlignPerRead + 1; // So the load can be used to judge if overflow
    if (P.bGetAllAlignments) {
        // WARNING if index indexTable.bExcludeAmbiguous will have some error
        alignmentsQ[0].setQueue_All_Best_OneFlag('A');
        alignmentsQ[1].setQueue_All_Best_OneFlag('A');
    }

    if (P.outputFormat[0] != '\0') {
        if (strcmp(P.outputFormat, "SAM") == 0 ||
                strcmp(P.outputFormat, "sam") == 0 ) {
            this->cOutputFormat = 's';
        } else if (strcmp(P.outputFormat, "MAPPING") == 0 ||
                   strcmp(P.outputFormat, "mapping") == 0 ) {
            this->cOutputFormat = 'm';
        } else if (strcmp(P.outputFormat, "FASTQ") == 0 ||
                   strcmp(P.outputFormat, "fastq") == 0 ) {
            this->cOutputFormat = 'F';
            this->opt.bPrintFirstAlignmentOnly = true;
        } else {
            LOG_INFO("Info %d: Specified output format %s is not recognizable.\n", WARNING_LOG, P.outputFormat);
        }
    }

    // check if the director exist, if not, create one
    if (P.outputDir[0] != '\0') {
        if (dirExist(P.outputDir) || createdir(P.outputDir) == 0) {
#ifdef  WIN32
            sprintf(this->opt.outputDir, "%s\\", P.outputDir);
#else
            sprintf(this->opt.outputDir, "%s/", P.outputDir);
#endif
        } else {
            LOG_INFO("Info %d: Can't create dir %s.\n", WARNING_LOG, P.outputDir);
            this->opt.outputDir[0] = '\0';
        }
    }
}

CReadsMapping::~CReadsMapping(void)
{
    delete AlignResult;
    delete MissReads;
}

void CReadsMapping::initialization(void)
{
    this->AlignResult = NULL;
    this->AmbiguousReads = NULL;
    this->MissReads = NULL;
    this->BadReads = NULL;
    this->cOutputFormat = 'm';
}

int CReadsMapping::mapReadsSets(const char* ReadsSetsList, GIndexTQ& table)
{
    // Counter for coverage is not initialize
    this->initializeStatsCounter();
    char readsFile[FILENAME_MAX];
    ifstream readsFileList(ReadsSetsList);
    while (GetNextFilenameFromListFile(readsFileList, readsFile)) {
        CReadInBitsSet readSet(readsFile, this->opt.readsFileFormat,\
                               table.uiRead_Length, this->opt.allowedNumOfNinRead, this->opt.truncatedReadPrefix);
        this->mapReads(readSet, table);
    }
    return(0);
}

int CReadsMapping::mapReads(CReadInBitsSet& readSet, const GIndexTQ& table)
{
    CAlignmentsQ& aQue = this->alignmentsQ[0];
    vector<CReadInBits>::iterator it = readSet.pReadsSet->begin();
    for (int i = 0; it != readSet.pReadsSet->end(); i++, it++) {
        this->printCheckPointInfo(i);
        readSet.get_read_id(i, aQue.tag);
        aQue.read = *it;
        aQue.qualityScores = readSet.getQScoresPtr(i);
        this->queryARead(*it, table, aQue);
        /*
        bool bMap2ForwardStrand = !opt.bMap2ReverseStrandOnly;
        bool bMap2reverseStrand = !opt.bMap2ForwardStrandOnly;

        if(bMap2ForwardStrand) {
            bool clearQ;
            table.queryARead(*it, aQue, clearQ = true, true);
        }
        if(bMap2reverseStrand) {
            bool clearQ = this->opt.bMap2ReverseStrandOnly ? true : false;
            table.queryARead(*it, aQue, clearQ, false);
        }*/
        // statistics and output
        if (aQue.load > 0) {
            bookKeepMapping(aQue);
            bool bPrintAlignment = this->printAlignmentOrNot(aQue, this->opt.bExcludeAmbiguousReads, this->opt.bPrintAmbiguousReadsOnly);
            if (bPrintAlignment) {
                this->dealMappedRead(table, aQue);
            }
        } else if (this->opt.bPrintUnMappedReads) {
            char qs[MAX_READ_LENGTH];
            qs[0] = '\0';
            if (readSet.pQualScores != NULL) {
                const char* qsPtr = readSet.pQualScores->qScores((unsigned int)i);
                unsigned qsShift = table.bMapReadInColors ? Phred_SCALE_QUAL_SHIFT : SolexaScoreEncodingShift;
                trQScores(table.uiRead_Length, qsShift, qsPtr, qs);
            }
            dealMissedRead(table.bMapReadInColors, aQue.tag, aQue.read, qs);
        }
    }
    iReadCounter += (unsigned int)readSet.size();
    return(0);
}

// To map reads longer than MAX_READ_LENGTH, locate alignments using first half and check the mismatches in the second half
int CReadsMapping::mapReads(CLongReadsSet& readSet, const GIndexTQ& table)
{
    CAlignmentsQ& aQue = this->alignmentsQ[0];
    CReadInBitsSet& readSet1stHalf = *(readSet.F_Reads);
    CReadInBitsSet& readSet2ndHalf = *(readSet.R_Reads);
    int bufferedReadNo = checkPairedReadSetSize(readSet1stHalf, readSet2ndHalf);
    vector<CReadInBits>::iterator it1, it2;
    it1 = readSet1stHalf.pReadsSet->begin();
    it2 = readSet2ndHalf.pReadsSet->begin();
    for (int i = 0; i < bufferedReadNo; i++, it1++, it2++) {
        this->printCheckPointInfo(i);
        CMappingResult m;
        this->getLongReadInfo(readSet, i, *it1, *it2, m);
        this->queryALongRead(*it1, *it2, table, aQue);
        // statistics and output
        if (aQue.load > 0) {
            bookKeepMapping(aQue);
            bool bPrintAlignment = this->printAlignmentOrNot(aQue, this->opt.bExcludeAmbiguousReads, this->opt.bPrintAmbiguousReadsOnly);
            if (bPrintAlignment) {
                this->dealMappedLongRead(table, aQue, m);
            }
        } else if (this->opt.bPrintUnMappedReads) {
            dealMissedRead(m);
        }
    }
    this->iReadCounter += bufferedReadNo;
    return(0);
}


// Map a single long read in two CReadInBits format
int CReadsMapping::queryALongRead(CReadInBits& r1stHalf, CReadInBits& r2ndHalf, const GIndexTQ& table, CAlignmentsQ& aQue) const
{
    // TODO: The reads should be query and "Check" immediately before que.
    // In this way, "short cut and filter can be applied right away. Should be changed
    /* Deprecated using the first half only for mapping
    table.queryReadBases(r1stHalf, aQue, true, true);
    table.queryReadBases(r1stHalf, aQue, false, false);
    table.extendAlignment(aQue, r2ndHalf);
    aQue.filterAlignments(this->opt.subDiffThreshold, this->opt.bGetAllAlignments);
    */
    /*
    if (table.bMapReadInColors) {
    	LOG_INFO("\nInfo %d: Currently PerM doesn't support mapping SOLiD reads longer than 64 base pairs.\n",\
             ERROR_LOG);
    }*/
    const bool bOddReadLength = this->opt.bOddReadLengthAndLongRead;
    const bool bAmbiguousOnly = this->opt.bPrintAmbiguousReadsOnly;
    const bool bShortCutAE = this->opt.bExcludeAmbiguousReads && aQue.qAllInThreshold() && !bAmbiguousOnly;
    const bool bShortCutE = this->opt.bExcludeAmbiguousReads && !bAmbiguousOnly;
    const bool bShortCutB = !this->opt.bExcludeAmbiguousReads && !aQue.qAllInThreshold() && !bAmbiguousOnly;
    const bool bMap2ForwardStrand = ! this->opt.bMap2ReverseStrandOnly;
    const bool bMap2ReverseStrand = ! this->opt.bMap2ForwardStrandOnly;

    if(bMap2ForwardStrand) {
        bool clearQ;
        table.queryALongRead(r1stHalf, r2ndHalf, bOddReadLength, aQue, 1, clearQ = true, true);
        if (aQue.load > 1) {
            if (bShortCutAE || (bShortCutE && aQue.MinDiff == 0)) {
                return (aQue.load);
            }
        }
        if (!(bShortCutB && aQue.MinDiff == 0)) {
            table.queryALongRead(r1stHalf, r2ndHalf, bOddReadLength, aQue, 2, clearQ = false, true);
            if (aQue.load > 1) {
                if (bShortCutAE || (bShortCutE && aQue.MinDiff == 0)) {
                    return (aQue.load);
                }
            }
        }
    }

    if(bMap2ReverseStrand) {
        bool clearQ = this->opt.bMap2ReverseStrandOnly ? true : false;
        table.queryALongRead(r1stHalf, r2ndHalf, bOddReadLength, aQue, 1, clearQ, false);
        if (aQue.load > 1) {
            if (bShortCutAE || (bShortCutE && aQue.MinDiff == 0)) {
                return (aQue.load);
            }
        }
        if (!(bShortCutB && aQue.MinDiff == 0)) {
            table.queryALongRead(r1stHalf, r2ndHalf, bOddReadLength, aQue, 2, clearQ = false, false);
        }
    }
    return(aQue.load);
}

int CReadsMapping::queryARead(CReadInBits& r, const GIndexTQ& table, CAlignmentsQ& aQue) const
{
    const bool bAmbiguousOnly = this->opt.bPrintAmbiguousReadsOnly;
    const bool bShortCutAE = this->opt.bExcludeAmbiguousReads && aQue.qAllInThreshold() && !bAmbiguousOnly;
    const bool bShortCutE = this->opt.bExcludeAmbiguousReads && !bAmbiguousOnly;
    const bool bMap2ForwardStrand = ! this->opt.bMap2ReverseStrandOnly;
    const bool bMap2ReverseStrand = ! this->opt.bMap2ForwardStrandOnly;

    if(bMap2ForwardStrand) {
        bool clearQ;
        table.queryARead(r, aQue, clearQ = true, true);
        if (aQue.load > 1) {
            if (bShortCutAE || (bShortCutE && aQue.MinDiff == 0)) {
                return (aQue.load);
            }
        }
    }

    if(bMap2ReverseStrand) {
        bool clearQ = this->opt.bMap2ReverseStrandOnly ? true : false;
        table.queryARead(r, aQue, clearQ, false);
        if (aQue.load > 1) {
            if (bShortCutAE || (bShortCutE && aQue.MinDiff == 0)) {
                return (aQue.load);
            }
        }
    }
    return(aQue.load);
}

int CReadsMapping::printLogFile(const char* inputFile)
{
    ofstream logFile(opt.logFileN, ofstream::app);
    if (logFile.good()) {
        this->printCommand(logFile, opt.fullCommand);
        this->printMappingStats(logFile, inputFile, opt.subDiffThreshold);
        this->printCommand(cout, opt.fullCommand);
        this->printMappingStats(cout, inputFile, opt.subDiffThreshold);
    } else {
        this->printCommand(cout, opt.fullCommand);
        this->printMappingStats(cout, inputFile, opt.subDiffThreshold);
    }
    logFile.close();
    return(0);
}

int CReadsMapping::dealMissedRead(bool bMapReadInColors, const char* readName, CReadInBits r, const char* qs)
{
    this->printRead(this->MissReads, bMapReadInColors, readName, r, qs);
    return(this->iMissReadCounter++);
}

int CReadsMapping::dealAmbiguousRead(bool bMapReadInColors, const char* readName, CReadInBits r, const char* qs)
{
    this->printRead(this->AmbiguousReads, bMapReadInColors, readName, r, qs);
    return(this->iReadsMapped2tooManyLocations);
}

inline void CReadsMapping::printRead(FileOutputBuffer* FileBuf, bool bMapReadInColors, const char* readName, CReadInBits r, const char* qs)
{
    char caRead[MAX_READ_LENGTH + 1];
    if (bMapReadInColors) {
        decodeColorReadWithPrimer(caRead, r, CReadInBits::iReadLength);
    } else {
        r.decode(caRead);
    }
    if (qs == NULL || qs[0] == '\0') {
        sprintf(FileBuf->caBufp, ">%s\n%s\n", readName, caRead);
    } else {
        sprintf(FileBuf->caBufp, "@%s\n%s\n+\n%s\n", readName, caRead, qs);
    }
    FileBuf->UpdateSize();
}

inline void CReadsMapping::printRead(FileOutputBuffer* FileBuf, CMappingResult& m)
{
    const char* readSeqOrColors;
    if (m.caRead[0] != '\0') {
        readSeqOrColors = m.caRead;
    } else {
        readSeqOrColors = m.TAG;
    }
    if (m.QScores[0] == '\0') {
        sprintf(FileBuf->caBufp, ">%s\n%s\n", m.QNAME, readSeqOrColors);
        FileBuf->UpdateSize();
    } else {
        sprintf(FileBuf->caBufp, "@%s\n%s\n+\n%s\n", m.QNAME, readSeqOrColors, m.QScores);
        FileBuf->UpdateSize();
    }
}

int CReadsMapping::dealMissedRead(CMappingResult& m)
{
    printRead(this->MissReads, m);
    return(this->iMissReadCounter++);
}

int CReadsMapping::dealAmbiguousRead(CMappingResult& m)
{
    printRead(this->AmbiguousReads, m);
    return(this->iReadsMapped2tooManyLocations);
}

void printSingleEndReads(char format, unsigned int uiReadLength);

// This function print out alignments for each read and the corresponding information.
int CReadsMapping::dealMappedRead(const GIndexTQ& table, CAlignmentsQ& aQue)
{
    bool samFormat = this->isSamFormat();
    CMappingResult m(aQue, opt.readLength);
    if (!table.bMapReadInColors) {
        // TODO: get read
        getReadQscores4Solexa(aQue, m, samFormat);
    }
    if (this->opt.bPrintAlignments) {
        if (aQue.load < aQue.iMaxCapacity || this->opt.bPrintAmbiguousReadsOnly) {
            for (unsigned int i = 0; i < aQue.load && i < aQue.iMaxCapacity; i++) {
                getSingleMappingInfo(table, aQue, i, m, samFormat, this->opt.bPrintNH);
                this->printSingleEndReads(m);
                if (this->opt.bPrintFirstAlignmentOnly) {
                    break;
                }
            }
        }
        if (aQue.load >= aQue.iMaxCapacity) {
            // If a read is mapped to over threshold place, don't print it
            this->iReadsMapped2tooManyLocations++;
            if (this->opt.bPrintAmbigReadsSeparately) {
                this->dealAmbiguousRead(table.bMapReadInColors,aQue.tag, aQue.read, aQue.qualityScores);
                /* The alternative ambiguous print will not include the adapter
                const bool IF_COLOR2BASE = false;
                const int FIRST_INDEX = 0;
                getSingleMappingInfo(table, aQue, FIRST_INDEX, m, IF_COLOR2BASE);
                this->dealAmbiguousRead(m);
                */
            } else if (this->opt.bPrintAmbigReadsInOneLine) {
                getSingleMappingInfo(table, aQue, 0, m, samFormat, this->opt.bPrintNH);
                this->printSingleEndReads(m);
            }
        }
    }
    return(aQue.load);
}

int CReadsMapping::dealMappedLongRead\
(const GIndexTQ& table, CAlignmentsQ& aQue, CMappingResult& m)
{
    bool samFormat = this->isSamFormat();
    if (aQue.load < aQue.iMaxCapacity || this->opt.bPrintAmbiguousReadsOnly) {
        if (this->opt.bPrintAlignments) {
            for (unsigned int i = 0; i < aQue.load && i < aQue.iMaxCapacity; i++) {
                // get and store all the output info in CMappingResult
                getLongMappingInfo(table, aQue, i, m, samFormat, this->opt.bPrintNH); // TODO  make change for SOLiD long read
                this->printSingleEndReads(m);
                if (this->opt.bPrintFirstAlignmentOnly) {
                    break;
                }
            }
        }
    }
    if (aQue.load >= aQue.iMaxCapacity) {
        // If a read is mapped to over threshold place, don't print it
        this->iReadsMapped2tooManyLocations++;
        if (this->opt.bPrintAmbigReadsSeparately) {
            this->dealAmbiguousRead(m);
        } else if (this->opt.bPrintAmbigReadsInOneLine) {
            getLongMappingInfo(table, aQue, 0, m, samFormat, this->opt.bPrintNH);
            this->printSingleEndReads(m);
        }
    }
    return(aQue.load);
}

int CReadsMapping::printSingleEndReads(CMappingResult& m)
{
    string dummyStr = "";
    int map_score = 0;
    switch (this->cOutputFormat) {
    case 's':
        printAMappingInSam(this->AlignResult, m);
        break;
    case 'F':
        printAMappingInFastq(this->AlignResult, m);
        break;
    case 'g':
        printAMappingInGff(this->AlignResult, m, map_score, dummyStr);
        break;
    default:
        printAMappingInPerM(this->AlignResult, m, this->opt.bPrintNM);
        break;
    }
    return(0);
}

/*
int CReadsMapping::printMappingsInBEDformat(FileOutputBuffer &OBuf, char* caKmer,
		CMapOfSingleRead& mapPatternsSet, unsigned int ReadID)
{
	const unsigned int SUB_LEVEL = 4;
	const char* RGB[SUB_LEVEL];
	RGB[0] = "0,0,0";
	RGB[1] = "0,0,255";
	RGB[2] = "0,255,0";
	RGB[3] = "255,0,0";

    unsigned int readLength = this->uiKmer_Length;
	for(int i = 0; i < mapPatternsSet.size; i++)
	{ // For each kind of pattern
		set<CSingleMapping,Comp>* pSet = &(mapPatternsSet.mappingsSets[i]);
		set<CSingleMapping,Comp>::iterator j;
		// Print each occurrence of a same pattern
		for(j = pSet->begin(); j != pSet->end(); j++) {
			unsigned int chrom = j->chrId;
			unsigned int chromStart = j->chrLocusId;
			unsigned int chromEnd = chromStart + readLength;
			int name = ReadID;
			int score = 1;

            if( score > 0) {
			    char strand = j->bReverse ? '-' : '+';
			    unsigned int subNum = diNtStrWildCardComp(mapPatternsSet.caPattern[i], caKmer, readLength);
			    const char* subRGB =  (subNum > (SUB_LEVEL - 1)) ? RGB[subNum] : RGB[SUB_LEVEL - 1] ;
			    sprintf(OBuf.caBufp,"chr%u %u %u read%d %d %c %d %d %s %d %d 0\n",
					    chrom, chromStart, chromEnd, name, score, strand,  chromStart, chromEnd,
					    subRGB, subNum, readLength);
			    OBuf.UpdateSize();
            } else {
                // bad read;
            }
		}
		// delete each set if it has been report
		mapPatternsSet.mappingsSets[i].clear();
	}
	return(mapPatternsSet.size);
} */

// This private function returns a string as the prefix of the output file name for mapping
string getOutputFileNamePrefix(const char* dir, const GIndexTQ& table, MappingOpts& opt)
{
    // The prefix contains 4 parts: (0) dir (1) ref file (2) ambiguous flag (3) mismatch threshold
    char outputPrefixStr[MAX_PATH];
    char excludeAmbiguous = opt.bExcludeAmbiguousReads ? 'E' : 'B';
    if (opt.bGetAllAlignments) excludeAmbiguous = 'A';
    sprintf(outputPrefixStr, "%s%s_%c_%u_%d",\
            dir, table.caRefName, excludeAmbiguous, table.chosenSeedId, opt.subDiffThreshold);
    return(string(outputPrefixStr));
}

bool isGalaxyOutputPath(const char* fileName)
{
    return(hasTheExtName(fileName, ".dat"));
}

bool isSupportedExtName(const char* fileN)
{
    if (hasTheExtName(fileN, ".fasta") ||
            hasTheExtName(fileN, ".fastq") ||
            hasTheExtName(fileN, ".fq") ||
            hasTheExtName(fileN, ".csfq") ||
            hasTheExtName(fileN, ".csfastq") ||
            hasTheExtName(fileN, ".csfasta") ||
            hasTheExtName(fileN, ".dat") ||
            hasTheExtName(fileN, ".txt")) {
        return(true);
    }
    return(false);
}

string rmSupportedExtName(const char* fileN)
{
    char newFileN[FILENAME_MAX];
    strcpy(newFileN, fileN);
    if (isSupportedExtName(fileN)) {
        for (int i = (int)strlen(newFileN); i > 0; i--) {
            if (newFileN[i] == '.') {
                newFileN[i] = '\0';
                return(string(newFileN));
            }
        }
    }
    return(string(fileN));
}

// generate the unmapped FileN, if the original one is not null
void getAmbiguousFileN(char* ambiguousFileN, const char* readFileN, const char* readFormat,\
                       const char* refN, bool bQual = false)
{
    string refNStr = rmSupportedExtName(refN);
    string readFileNStr = rmSupportedExtName(readFileN);
    if (strcmp(ambiguousFileN, "") == 0) { // make sure the readFormat is valid
        if (strcmp(readFormat, "csfasta") == 0) {
            if (bQual) {
                sprintf(ambiguousFileN, "%s_ambig_%s.%s", readFileNStr.c_str(), refNStr.c_str(), "fastq");
            } else {
                sprintf(ambiguousFileN, "%s_ambig_%s", readFileNStr.c_str(), refNStr.c_str()); // Not sure csfasta or not
            }
        } else {
            if (readFormat[0] == '\0') {
                sprintf(ambiguousFileN, "%s_ambig_%s", readFileNStr.c_str(), refNStr.c_str());
            } else {
                sprintf(ambiguousFileN, "%s_ambig_%s.%s", readFileNStr.c_str(), refNStr.c_str(), readFormat);
            }
        }
    }
}

// generate the unmapped FileN, if the original one is not null
void getUnmappedFileN(char* unmappedFileN, const char* readFileN, const char* readFormat,\
                      const char* refN, bool bQual = false)
{
    string refNStr = rmSupportedExtName(refN);
    string readFileNStr = rmSupportedExtName(readFileN);
    if (strcmp(unmappedFileN, "") == 0) { // make sure the readFormat is valid
        if (strcmp(readFormat, "csfasta") == 0) {
            if (bQual) {
                sprintf(unmappedFileN, "%s_miss_%s.%s", readFileNStr.c_str(), refNStr.c_str(), "fastq");
            } else {
                sprintf(unmappedFileN, "%s_miss_%s", readFileNStr.c_str(), refNStr.c_str()); // Not sure csfasta or not
            }
        } else {
            if (readFormat[0] == '\0') {
                sprintf(unmappedFileN, "%s_miss_%s", readFileNStr.c_str(), refNStr.c_str());
            } else {
                sprintf(unmappedFileN, "%s_miss_%s.%s", readFileNStr.c_str(), refNStr.c_str(), readFormat);
            }
        }
    }
}

// generate the unmapped FileN, if the original one is not null
void getBadReadsFileN(char* badFileN, const char* readFileN, const char* readFormat,\
                      const char* refN, bool bQual = false)
{
    string refNStr = rmSupportedExtName(refN);
    string readFileNStr = rmSupportedExtName(readFileN);
    if (strcmp(badFileN, "") == 0) { // make sure the readFormat is valid
        if (strcmp(readFormat, "csfasta") == 0) {
            if (bQual) {
                sprintf(badFileN, "%s_bad.%s", readFileNStr.c_str(), "fastq");
            } else {
                sprintf(badFileN, "%s_bad", readFileNStr.c_str()); // Not sure csfasta or not
            }
        } else {
            if (readFormat[0] == '\0') {
                sprintf(badFileN, "%s_bad", readFileNStr.c_str());
            } else {
                sprintf(badFileN, "%s_bad.%s", readFileNStr.c_str(), readFormat);
            }
        }
    }
}

string CReadsMapping::getMappingFileN(const char* caReadsSetName,  const GIndexTQ& table)
{
    if (this->cOutputFormat == 'm') {
        strcpy(opt.outputFormat, "mapping");
    } else if (this->cOutputFormat == 's') {
        strcpy(opt.outputFormat, "sam");
    } else if (this->cOutputFormat == 'F') {
        strcpy(opt.outputFormat, "fastq");
    }

    char outputPath[MAX_LINE];
    if (this->opt.outputFileN[0] == '\0') { // No output file has been set
        string outFileNPrefix = getOutputFileNamePrefix(this->opt.outputDir, table, opt);
        string fileNameOfReadSet = getBasename(caReadsSetName);
        const char* extN = opt.outputFormat;
        if (string(extN) == "sam" || string(extN) == "mapping" || string(extN) == "fastq") {
            sprintf(outputPath, "%s_%s.%s", outFileNPrefix.c_str(), fileNameOfReadSet.c_str(), extN);
        } else {
            if (extN[0] != '0') {
                char msg[MAX_LINE];
                sprintf(msg, "The specified output format %s is unrecognizable.\n", extN);
                cout << msg;
                // LOG_INFO("%s", WARNING_LOG, msg);
            }
            sprintf(outputPath, "%s_%s", outFileNPrefix.c_str(), fileNameOfReadSet.c_str());
        }
    } else {
        sprintf(outputPath, "%s%s", this->opt.outputDir, this->opt.outputFileN);
    }
    return(string(outputPath));
}

int CReadsMapping::setUpIO4Aligment(const char* caReadsSetName, const GIndexTQ& table)
{
    if (this->opt.bGetAllAlignments) {
        // Find all alignments with in threshold, instead of queuing
        // only all best(with the fewest mismatches) alignments as default.
        alignmentsQ[0].setQueue_All_Best_OneFlag('A');
        alignmentsQ[1].setQueue_All_Best_OneFlag('A');
    }
    char outputPath[MAX_PATH];
    strcpy(outputPath, this->getMappingFileN(caReadsSetName, table).c_str());

    // (1)Initialize the I/O for output the alignments
    if (this->opt.bPrintAlignments) {
        if (this->AlignResult == NULL) {
            ofstream* AlignResultFile = new ofstream(outputPath);
            this->AlignResult =
                new FileOutputBuffer(ALIGNMENT_RESULT_FILE_BUFFER_SIZE, AlignResultFile);
            if (this->AlignResult == NULL) {
                ERR;//check new FileOutputBuffer
                return(1);
            } else if (this->isSamFormat()) {
                if (this->opt.bPrintSamHeader) {
                    string RG = getSamRG(caReadsSetName, table.bMapReadInColors);
                    vector<CGene> refs = table.pgenomeNT->getRefNamesLengths();
                    string commandLineStr = this->opt.fullCommand;
                    printSamHeader(AlignResult, refs, RG.c_str(), commandLineStr.c_str());
                }
            }
        }
    }
    // (2)Initialize the I/O for output the up-mapped reads.
    if (this->opt.bPrintUnMappedReads) {
        // Make sure opt.readsFileFormat has been set
        const char* refN = table.pgenomeNT->refName;
        getUnmappedFileN(opt.unmappedFileN, caReadsSetName, opt.readsFileFormat, refN);
        /* // This line force the output to be the right ext name (fasta, csfasta or fastq)
        if (!isGalaxyOutputPath(opt.unmappedFileN)) {
            string extName = string(".").append(string(opt.readsFileFormat));
            chExtName(opt.unmappedFileN, extName.c_str());
        }*/
        if (this->MissReads == NULL) {
            ofstream* MissReadsFile = new ofstream(opt.unmappedFileN);
            this->MissReads =
                new FileOutputBuffer(ALIGNMENT_RESULT_FILE_BUFFER_SIZE, MissReadsFile);
            if (this->MissReads == NULL) {
                ERR; // check new FileOutputBuffer
                return(1);
            }
        }
    }
    // (3) Initialize the I/O for output the ambiguous reads over the threshold
    if (this->opt.bPrintAmbigReadsSeparately) {
        // Make sure opt.readsFileFormat has been set
        const char* refN = table.pgenomeNT->refName;
        getAmbiguousFileN(opt.ambiguousReadFileN, caReadsSetName, opt.readsFileFormat, refN);
        if (this->AmbiguousReads == NULL) {
            ofstream* ambiguousReadsFile = new ofstream(opt.ambiguousReadFileN);
            this->AmbiguousReads =
                new FileOutputBuffer(ALIGNMENT_RESULT_FILE_BUFFER_SIZE, ambiguousReadsFile);
            if (this->AmbiguousReads == NULL) {
                ERR; // check new FileOutputBuffer
                return(1);
            }
        }
    }

    // (4) Initialize the I/O for output the bad reads or reads shorter than expected
    if (this->opt.bPrintBadReads) {
        // Make sure opt.readsFileFormat has been set
        const char* refN = table.pgenomeNT->refName;
        getBadReadsFileN(opt.badReadFileN, caReadsSetName, opt.readsFileFormat, refN);
        if (this->BadReads == NULL) {
            ofstream* badReadsFile = new ofstream(opt.badReadFileN);
            this->BadReads =
                new FileOutputBuffer(ALIGNMENT_RESULT_FILE_BUFFER_SIZE, badReadsFile);
            if (this->BadReads == NULL) {
                ERR; // check new FileOutputBuffer
                return(1);
            }
        }
    }
    return(0);
}

int CReadsMapping::tearDownIO4Aligment(void)
{
    // close file, which has been opened before
    if (this->opt.bPrintAlignments) {
        // this->AlignResult->removeEndBlankLine();
        delete this->AlignResult;
        this->AlignResult = NULL;
    }
    if (this->opt.bPrintUnMappedReads) {
        // this->MissReads->removeEndBlankLine();
        delete this->MissReads;
        this->MissReads = NULL;
    }
    if (this->opt.bPrintAmbigReadsSeparately) {
        delete this->AmbiguousReads;
        this->AmbiguousReads = NULL;
    }
    if (this->opt.bPrintBadReads) {
        delete this->BadReads;
        this->BadReads = NULL;
    }
    return(0);
}

void getSingleMappingSeq4Solexa(const GIndexTQ& table, CMappingResult& m, bool noRef)
{
    if (noRef) { // sam format doesn't need to know reference sequence
        m.caRef[0] = '\0';
    } else {
        const unsigned int readLength = (unsigned int)strlen(m.caRead);
        // (1) Get ref in bits
        CReadInBits ref = table.pgenomeNTInBits->getSubstringInBits(m.uiGlobalMappedPos, readLength);
        if (m.strand == '-') {
            ref = reverseCompliment(ref, readLength);
        }
        // (2) Get ref seq
        ref.decode(m.caRef);
    }
}

void getQscores4Solexa(CAlignmentsQ& aQue, CMappingResult& m, bool samFormat)
{
    if (aQue.qualityScores == NULL) {
        m.mismatchScore = (int)m.uiDiff;
        m.QScores[0] = '\0';
    } else { // If quality score are available, get quality score
        m.mismatchScore = alignmentScore(m.caRead, m.caRef, m.uiReadLength, aQue.qualityScores);
        trQScores(m.uiReadLength, SolexaScoreEncodingShift, aQue.qualityScores, m.QScores);
        if (samFormat) {
            m.getReverseReadandQual();
        }
    }
}

void getReadQscores4Solexa(CAlignmentsQ& aQue, CMappingResult& m, bool samFormat)
{
    aQue.read.decode(m.caRead);
    getQscores4Solexa(aQue, m, samFormat);
}

unsigned int getNoOfDiff(const char* str1, const char* str2, unsigned int readLength)
{
    unsigned int noOfDiff = 0;
    for (unsigned int i = 0; i < readLength; i++) {
        if (str1[i] != str2[i]) {
            noOfDiff ++;
        }
    }
    return(noOfDiff);
}

// For one read to one location,
void getSingleMappingInfo(const GIndexTQ& table, CAlignmentsQ& aQue, unsigned int mappingId, CMappingResult& m, bool samFormat, bool bNH)
{
    getSingleMappingIndex(*(table.pgenomeNT), aQue, mappingId, m);
    if (samFormat) { // must set after the strand flag is set
        m.setSingleEndSamFields(bNH);
    } // must set before the color seq and QUAL is set
    if (table.bMapReadInColors) {
        getSingleMappingSeqAndQ4SOLiD(table, aQue, aQue.read, m, samFormat);
    } else {
        getSingleMappingSeq4Solexa(table, m, samFormat);
    }
    if (aQue.qAllInThreshold() && !samFormat) {
        int noMisMatch = getNoOfDiff(m.caRead, m.caRef, m.uiReadLength);
        /* if(m.uiDiff != noMisMatch) {
            ERR;
        }*/
        m.uiDiff = noMisMatch;
    }
    if (table.pbaRepeatRepresentativeFlag->b(aQue.aiHitIndex[mappingId])) {
        m.MultipleMappedNo++;
    }
}


void getLongMappingInfo(const GIndexTQ& table, CAlignmentsQ& aQue, unsigned int mappingId, CMappingResult& m,\
                        bool samFormat, bool bNH)
{
    // (1) Get index
    getSingleMappingIndex(*(table.pgenomeNT), aQue, mappingId, m);
    if (samFormat) {
        m.setSingleEndSamFields(bNH);
    } else {
        // (2) Get Ref Sequence
        getLongRefSeq(table, m, samFormat);
        // (3) Get mismatch score
        if (m.QScores[0] == '\0') {
            m.mismatchScore = (int)m.uiDiff;
        } else { // If quality score are available, get quality score
            m.mismatchScore = alignmentScore(m.caRead, m.caRef, m.uiReadLength, m.rawScores);
        }
        if (m.strand == '-') {
            reverseComplementKmer(m.caRef);
        }
    }
}

template <class ReadSetType>
int CReadsMapping::mapReadsInAFile (ReadSetType& readSet, const GIndexTQ& table)
{
    if (wrongIndex(readSet, table)) return(0);
    getReadsFileFormat(readSet.InputFile, opt.readsFileFormat);
    string seedStr = seedSymbol(table.chosenSeedId);
    unsigned int uiReadLength = readSet.getReadLength();
    printf("Mapping %s (%u-bp reads) with %s seed.%s\n", \
           readSet.InputFile, uiReadLength, seedStr.c_str(), BLANK_LINE);
    // if there are multiple gene in first chromosome.
    // bool bPrintGeneName = (table.pgenomeNT->paChromosomes[0]->geneVec. table.size() >= 0);
    this->initializeStatsCounter();
    if (this->setUpIO4Aligment(readSet.InputFile, table) != 0) {
        LOG_INFO("\nInfo %d: Fail to setup I/O files.", ERROR_LOG);
        return(1);
    }
    readSet.setBadReadOutputFile(this->BadReads);
    while (readSet.get_next_capacity_reads(BUFFERED_READS_SIZE, opt.readtag_delimiter) > 0) {
        this->mapReads(readSet, table);
    }
    this->tearDownIO4Aligment();
    this->iBadReadCounter = readSet.uiNo_of_Bad_Reads;
    this->printMappingStats(cout, readSet.InputFile, opt.subDiffThreshold);
    return(0);
}